/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "nvs.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_event.h"

#include "esp_psram.h"

#include "esp_bridge.h"
#if defined(CONFIG_APP_BRIDGE_USE_WEB_SERVER)
#include "web_server.h"
#endif
#include "iot_button.h"
#if defined(CONFIG_APP_BRIDGE_USE_WIFI_PROVISIONING_OVER_BLE)
#include "wifi_prov_mgr.h"
#include <wifi_provisioning/manager.h>
#endif

#define BUTTON_NUM            1
#define BUTTON_SW1            CONFIG_APP_GPIO_BUTTON_SW1
#define BUTTON_PRESS_TIME     5000000
#define BUTTON_REPEAT_TIME    5

static const char *TAG = "main";
static button_handle_t g_btns[BUTTON_NUM] = { 0 };
static bool button_long_press = false;
static esp_timer_handle_t restart_timer;
static esp_timer_handle_t restart_after_prov_timer = NULL;


static esp_err_t esp_storage_init(void)
{
    esp_err_t ret = nvs_flash_init();

    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    return ret;
}

static void button_press_up_cb(void *hardware_data, void *usr_data)
{
    ESP_LOGI(TAG, "BTN: BUTTON_PRESS_UP");

    if (button_long_press) {
        ESP_ERROR_CHECK(esp_timer_stop(restart_timer));
        button_long_press = false;
    }
}

static void button_press_repeat_cb(void *hardware_data, void *usr_data)
{
    uint8_t press_repeat = iot_button_get_repeat((button_handle_t)hardware_data);
    ESP_LOGI(TAG, "BTN: BUTTON_PRESS_REPEAT[%d]", press_repeat);
}

static void button_long_press_start_cb(void *hardware_data, void *usr_data)
{
    ESP_LOGI(TAG, "BTN: BUTTON_LONG_PRESS_START");
    button_long_press = true;
    ESP_ERROR_CHECK(esp_timer_start_once(restart_timer, BUTTON_PRESS_TIME));
}

static void restart_timer_callback(void *arg)
{
    ESP_LOGI(TAG, "Restore factory settings");
    nvs_flash_erase();
    esp_restart();
}

static void esp_bridge_create_button(void)
{
    const esp_timer_create_args_t restart_timer_args = {
        .callback = &restart_timer_callback,
        .name = "restart"
    };
    ESP_ERROR_CHECK(esp_timer_create(&restart_timer_args, &restart_timer));

    button_config_t cfg = {
        .type = BUTTON_TYPE_GPIO,
        .gpio_button_config = {
            .gpio_num = BUTTON_SW1,
            .active_level = 0,
        },
    };
    g_btns[0] = iot_button_create(&cfg);
    iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb, 0);
    iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb, 0);
    iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb, 0);
}
static void mem_timer_callback(void* arg)
{
    static size_t last_min_heap_size = 0;
    static size_t last_heap_size = 0;

    size_t current_min_heap_size = esp_get_minimum_free_heap_size();
    if (current_min_heap_size != last_min_heap_size) {
        printf("Minimum free heap size: %d bytes \r\n", current_min_heap_size);
        last_min_heap_size = current_min_heap_size;
    }

    size_t current_heap_size = esp_get_free_heap_size();
    if(current_heap_size != last_heap_size) {
        printf("Free heap size: %d bytes \r\n", current_heap_size);
        last_heap_size = current_heap_size;
    }

    // ESP_LOGE(TAG, "Free heap size: %d bytes", current_heap_size
    // ESP_LOGE(TAG,"Minimum free heap size: %d bytes", current_min_heap_size);
}

void mem_analyze_init(void)
{
    esp_timer_handle_t analyze_timer;
    const esp_timer_create_args_t timer_args = {
            .callback = &mem_timer_callback,
            .name = "mem_timer"
    };
    ESP_LOGI(TAG,"create analyze_timer");
    ESP_ERROR_CHECK(esp_timer_create(&timer_args,
                                     &analyze_timer));
    esp_timer_start_periodic(analyze_timer,
                             1000 * 1000); //周期，单位us
    ESP_LOGI(TAG,"start analyze_timer");
}
static void restart_after_prov_timer_callback(void *arg)
{
    ESP_LOGI(TAG, "Provisioning finished, restarting device in 3 seconds...");
    esp_restart();
}
/* Event handler for catching system events */
static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{

    ESP_LOGI(TAG, "event_handler event_base: %s, event_id: %ld", event_base, event_id);
    if (event_base == WIFI_PROV_EVENT) {
        switch (event_id) {
        case WIFI_PROV_START:
            ESP_LOGI(TAG, "Provisioning started");
            break;

        case WIFI_PROV_CRED_RECV: {
            wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
            ESP_LOGI(TAG, "Received Wi-Fi credentials"
                     "\n\tSSID     : %s\n\tPassword : %s",
                     (const char *)wifi_sta_cfg->ssid,
                     (const char *)wifi_sta_cfg->password);
            break;
        }

        case WIFI_PROV_CRED_FAIL: {
            wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
            ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s"
                     "\n\tPlease reset to factory and retry provisioning",
                     (*reason == WIFI_PROV_STA_AUTH_ERROR) ?
                     "Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
            break;
        }

        case WIFI_PROV_CRED_SUCCESS:
            ESP_LOGI(TAG, "Provisioning successful");
            // Create and start a timer to restart the device after 3 seconds
            const esp_timer_create_args_t restart_after_prov_timer_args = {
                .callback = &restart_after_prov_timer_callback,
                .name = "restart_after_prov"
            };
            ESP_ERROR_CHECK(esp_timer_create(&restart_after_prov_timer_args, &restart_after_prov_timer));
            ESP_ERROR_CHECK(esp_timer_start_once(restart_after_prov_timer, 3 * 1000000)); // 3 seconds
            break;

        default:
            break;
        }
    }
}
static void wifi_prov_event_register(void)
{
    /* Register our event handler for Wi-Fi, IP and Provisioning related events */
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
}
void app_main(void)
{
    esp_log_level_set("*", ESP_LOG_INFO);

    esp_storage_init();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_bridge_create_all_netif();

    size_t psram_size = esp_psram_get_size();
    if (psram_size == 0) {
        ESP_LOGW(TAG, "No PSRAM detected!");
        return;
    }

    ESP_LOGI(TAG, "PSRAM initialized, size: %d bytes (%.2f MB)", 
             psram_size, psram_size / (1024.0 * 1024.0));

    mem_analyze_init();

#if defined(CONFIG_BRIDGE_DATA_FORWARDING_NETIF_SOFTAP)
    wifi_config_t wifi_cfg = {
        .ap = {
            .ssid = CONFIG_BRIDGE_SOFTAP_SSID,
            .password = CONFIG_BRIDGE_SOFTAP_PASSWORD,
        }
    };
    esp_bridge_wifi_set_config(WIFI_IF_AP, &wifi_cfg);
#endif
#if defined(CONFIG_BRIDGE_EXTERNAL_NETIF_STATION)
    esp_wifi_connect();
#endif
    esp_bridge_create_button();

#if defined(CONFIG_APP_BRIDGE_USE_WEB_SERVER)
    StartWebServer();
#endif /* CONFIG_APP_BRIDGE_USE_WEB_SERVER */
#if defined(CONFIG_APP_BRIDGE_USE_WIFI_PROVISIONING_OVER_BLE)
    // Check if the device is already provisioned
    bool is_provisioned = false;
    esp_err_t ret = wifi_prov_mgr_is_provisioned(&is_provisioned);
    if (ret == ESP_OK && is_provisioned) {
        ESP_LOGI(TAG, "Device is already provisioned, starting Wi-Fi connection...");
    } else {
        ESP_LOGI(TAG, "Device is not provisioned, starting BLE provisioning...");
        esp_bridge_wifi_prov_mgr();
        wifi_prov_event_register();
    }
#endif /* CONFIG_APP_BRIDGE_USE_WIFI_PROVISIONING_OVER_BLE */
}
